Imports Aricie.DNN.Caching.Analysis
Imports Aricie.DNN.Diagnostics
Imports Aricie.DNN.Services.Workers
Imports System.Threading
Imports Aricie.Collections

Namespace Aricie.DNN.Caching.Client
    Public Class CachingClientController
        Implements IDisposable


        Private _ClientSettings As CachingClientSettings


        Private _LastCheck As DateTime
        Private _BroadCastExpriation As TimeSpan = TimeSpan.Zero
        Private WithEvents _ResetTimeOutTaskQueue As TaskQueue(Of CachePutJob)
        Private _EmulatedCallBacksTimer As System.Threading.Timer
        Private _EmulatedCallBacks As New Dictionary(Of String, CacheItemRemovedCallback)

        Private ReadOnly Property BroadCastExpiration() As TimeSpan
            Get
                If _BroadCastExpriation = TimeSpan.Zero Then
                    For Each cluster As CachingClusterSettings In Me._ClientSettings.AvailableClusters.Values
                        If cluster.PollingInterval.Value.Ticks * 2 > _BroadCastExpriation.Ticks Then
                            BroadCastExpiration = TimeSpan.FromTicks(cluster.PollingInterval.Value.Ticks * 2)
                        End If
                    Next
                End If
                Return _BroadCastExpriation
            End Get
        End Property


        Private ReadOnly Property ResetTimeOutTaskQueue() As TaskQueue(Of CachePutJob)
            Get
                If _ResetTimeOutTaskQueue Is Nothing Then
                    _ResetTimeOutTaskQueue = New TaskQueue(Of CachePutJob)(New Action(Of CachePutJob)(AddressOf Me.ResetTimeOut), DistributedCachingProviderBase.Settings.CloudSettings.ResetTimeOutTaskQueueInfo)
                    'AddHandler _PersisterTaskQueue.ActionPerformed, AddressOf Me.PersisterTaskQueue_ActionPerformed
                End If
                Return _ResetTimeOutTaskQueue
            End Get
        End Property


        Public Sub New()
            Me.New(DistributedCachingProviderBase.Settings.ClientSettings)
        End Sub


        Public Sub New(ByVal clientSettings As CachingClientSettings)
            _ClientSettings = clientSettings
        End Sub



#Region "Public methods"

        'Public Function [Get](ByVal key As String) As Object

        '    Return [Get](DistributedCachingProvider.Settings.CloudSettings.GetKeyJob(key))

        'End Function

        Public Function [Get](ByVal objCacheJob As CacheJob) As WrappedCacheObject

            Return [Get](objCacheJob, False)

        End Function

        Public Function [Get](ByVal objCacheJob As CacheJob, ByVal nolog As Boolean) As WrappedCacheObject




            Dim toReturn As WrappedCacheObject = Nothing
            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
            If Not clusterClient.Disabled Then
                Try
                    If Not nolog AndAlso objCacheJob.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingGet, "Get Start DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.EndOverhead, False, objCacheJob.Key, False)
                    End If
                    toReturn = clusterClient.Get(objCacheJob)
                    If Not nolog AndAlso objCacheJob.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingGet, "Get End DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.InProgress, False, objCacheJob.Key, True)
                    End If
                    If toReturn IsNot Nothing _
                                    AndAlso toReturn.SlidingExpriration <> Cache.NoSlidingExpiration _
                                       AndAlso Not clusterClient.ProviderSettings.SupportsSlidingExpiration _
                                       AndAlso Not objCacheJob.Strategy.DisableSlidingEmulation _
                                       AndAlso Not objCacheJob.Strategy.TimeOutMode = TimeOutMode.NoTimeOut Then
                        ResetTimeOutTaskQueue.EnqueueTask(DistributedCachingProviderBase.Settings.CloudSettings.GetPutJob(objCacheJob, toReturn))
                    End If

                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                End Try

                'If Not clusterClient.ProviderSettings.SupportsConcurrency AndAlso objCacheJob.Strategy.ConcurrencyMode <> ConcurrencyMode.NoConcurrency Then
                '    DirectCast(toReturn, WrappedCacheObject).CacheItemVersion

                'End If
            End If

            Return toReturn



        End Function

        Public Function GetWithTags(ByVal objCacheJob As CacheJob) As IEnumerable(Of KeyValuePair(Of String, WrappedCacheObject))


            Dim toReturn As IEnumerable(Of KeyValuePair(Of String, WrappedCacheObject)) = Nothing
            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
            If Not clusterClient.Disabled Then
                Try
                    If objCacheJob.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingGet, "GetByTag Start DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.EndOverhead, False, objCacheJob.Strategy.GetTags, False)
                    End If
                    toReturn = clusterClient.MultipleGet(objCacheJob)
                    If objCacheJob.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingGet, "GetByTag End DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.InProgress, False, objCacheJob.Strategy.GetTags, True)
                    End If
                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                End Try
            End If

            Return toReturn

        End Function

        Public Function Put(ByVal objCacheJob As CachePutJob) As Boolean


            Return Me.Put(objCacheJob, Nothing)


        End Function


        Public Function Put(ByVal objCacheJob As CachePutJob, ByVal itemRemovedCallback As System.Web.Caching.CacheItemRemovedCallback) As Boolean
            Dim result As Boolean
            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob.Job)
            If Not clusterClient.Disabled Then
                Try
                    If objCacheJob.Job.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingInsert, "Put Start DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.EndOverhead, False, objCacheJob.Job.Key, False)
                    End If

                    Try
                        If objCacheJob.Value.GetType Is GetType(WrappedDistributedCacheObject) Then
                            Dim objWrapped As WrappedDistributedCacheObject = DirectCast(objCacheJob.Value, WrappedDistributedCacheObject)
                            objWrapped.ProcessSerialization()
                        End If
                        result = clusterClient.Put(objCacheJob)
                    Catch ex As Exception
                        Dim wrappingex As New ApplicationException(String.Format("Could not serialize object with key {0} and serialization mode {1}", objCacheJob.Job.Key, objCacheJob.Job.Strategy.DistributionType.ToString()), ex)
                        Try
                            Throw wrappingex
                        Catch wapperEx As Exception
                            Aricie.DNN.Diagnostics.AsyncLogger.Instance.AddException(wapperEx)
                            result = False
                        End Try
                    End Try
                    If objCacheJob.Job.ShouldLog Then
                        DistributedCachingBusinessController.LogCachingOperation(CacheTimings.DistributedCachingInsert, "Put End DistributedCaching Request", _
                                                                                 Aricie.DNN.Diagnostics.WorkingPhase.InProgress, False, objCacheJob.Job.Key, result)
                    End If
                    If result Then
                        If itemRemovedCallback IsNot Nothing Then
                            If clusterClient.ProviderSettings.SupportsNotifications Then
                                'Dim adapter As New CacheCallBackAdapter(itemRemovedCallback)
                                result = Me.AddCallBack(objCacheJob.Job, itemRemovedCallback)
                            Else
                                _EmulatedCallBacks(objCacheJob.Job.Key) = itemRemovedCallback
                                If _EmulatedCallBacksTimer Is Nothing Then
                                    _EmulatedCallBacksTimer = New Timer(New TimerCallback(AddressOf CheckCallBacks), Nothing, _
                                                                       clusterClient.ClusterSettings.PollingInterval.Value, _
                                                                       clusterClient.ClusterSettings.PollingInterval.Value)
                                End If
                                Me.BroadcastChange(objCacheJob.Job.Key)

                            End If
                        End If
                        If objCacheJob.Job.Strategy.PutTags <> "" AndAlso Not clusterClient.ProviderSettings.SupportsTags Then
                            For Each strTag As String In objCacheJob.Job.Strategy.PutTagsList
                                clusterClient.AddTag(objCacheJob.Job.Key, strTag)
                            Next
                            If clusterClient.UpdateTagsCatalog Then
                                clusterClient.UpdateTagsCatalog = False
                                Dim toInsert As New SerializableDictionary(Of String, List(Of String))(clusterClient.TagsCatalog)
                                DistributedCachingProviderBase.Instance.Insert(DistributedCachingProviderBase.Settings.CloudSettings.TagsCatalogKey, toInsert, False)
                            End If

                        End If
                    Else
                        If DistributedCachingProviderBase.Settings.LoggingSettings.EnableDebugLogging Then
                            Dim failedKey As New KeyValuePair(Of String, String)(CacheStepProperty.CacheKey, objCacheJob.Job.Key)
                            Dim objDebug As New DebugInfo(DistributedCachingBusinessController.glbDistributedCachingLogType, "Put Failure", failedKey)
                            Aricie.DNN.Diagnostics.SimpleDebugLogger.Instance.AddDebugInfo(objDebug)
                        End If

                        DistributedCachingProviderBase.Instance.Remove(objCacheJob.Job.Key)
                    End If

                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                    result = False
                End Try
            End If
            Return result
        End Function


        Public Sub ResetTimeOut(ByVal objCacheJob As CachePutJob)

            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob.Job)
            If Not clusterClient.Disabled Then
                Try
                    clusterClient.ResetTimeOut(objCacheJob)
                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                End Try
            End If

        End Sub


        Public Sub Remove(ByVal objCacheJob As CacheJob)

            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
            If Not clusterClient.Disabled Then
                Try
                    clusterClient.Remove(objCacheJob)
                    If objCacheJob.Strategy.ConcurrencyMode <> ConcurrencyMode.NoConcurrency Then
                        clusterClient.RemoveItemVersion(objCacheJob.Key)
                    End If
                    If Not clusterClient.ProviderSettings.SupportsNotifications AndAlso Me._EmulatedCallBacks.ContainsKey(objCacheJob.Key) Then
                        Me.BroadcastChange(objCacheJob.Key)
                    End If
                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                End Try
            End If

        End Sub

        Public Function AddCallBack(ByVal objCacheJob As CacheJob, ByVal itemRemovedCallback As CacheItemRemovedCallback) As Boolean
            Dim toReturn As Boolean
            Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
            If Not clusterClient.Disabled Then
                Try
                    If clusterClient.ProviderSettings.SupportsNotifications Then
                        toReturn = clusterClient.AddCallBack(objCacheJob, itemRemovedCallback)
                    Else
                        toReturn = True
                    End If
                Catch ex As Exception
                    Me.ProcessException(clusterClient, ex)
                End Try
            End If
            Return toReturn
        End Function

        Public Sub CreateRegion(ByVal objCacheJob As CacheJob)

            If objCacheJob.Strategy.RegionName <> "" Then
                Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
                If Not clusterClient.Disabled Then
                    Try
                        clusterClient.CreateRegion(objCacheJob)
                    Catch ex As Exception
                        Me.ProcessException(clusterClient, ex)
                    End Try
                End If
            End If
        End Sub



        Public Sub ClearRegion(ByVal objCacheJob As CacheJob)

            If objCacheJob.Strategy.RegionName <> "" Then
                Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
                If Not clusterClient.Disabled Then
                    Try
                        clusterClient.ClearRegion(objCacheJob)
                    Catch ex As Exception
                        Me.ProcessException(clusterClient, ex)
                    End Try
                End If
            End If

        End Sub



        Public Sub DeleteRegion(ByVal objCacheJob As CacheJob)

            If objCacheJob.Strategy.RegionName <> "" Then
                Dim clusterClient As CachingClientProvider = Me.GetClientProvider(objCacheJob)
                If Not clusterClient.Disabled Then
                    Try
                        clusterClient.DeleteRegion(objCacheJob)
                    Catch ex As Exception
                        Me.ProcessException(clusterClient, ex)
                    End Try
                End If
            End If
        End Sub

        Public Sub [Stop]()
            For Each objCluster As CachingClusterSettings In Me._ClientSettings.AvailableClusters.Values
                Try
                    objCluster.ClientProvider.Dispose()
                Catch ex As Exception
                    Aricie.DNN.Diagnostics.AsyncLogger.Instance.AddException(ex)
                End Try
            Next
            If Me._EmulatedCallBacksTimer IsNot Nothing Then
                Me._EmulatedCallBacksTimer.Dispose()
            End If

        End Sub


        Public Sub StopTemporary()

            For Each objCluster As CachingClusterSettings In Me._ClientSettings.AvailableClusters.Values
                Try
                    objCluster.ClientProvider.DisableTemporary()
                Catch ex As Exception
                    Aricie.DNN.Diagnostics.AsyncLogger.Instance.AddException(ex)
                End Try
            Next
        End Sub

#End Region

#Region "Private methods"

        Private Function GetClientProvider(ByVal objCacheJob As CacheJob) As CachingClientProvider
            Dim cluster As CachingClusterSettings = Nothing
            If Me._ClientSettings.AvailableClusters.TryGetValue(objCacheJob.Strategy.ClusterName, cluster) Then
                Return cluster.ClientProvider
            ElseIf Me._ClientSettings.AvailableClusters.Count > 0 Then
                Return Me._ClientSettings.AvailableClusters.ToList(0).Value.ClientProvider
            Else
                Return Nothing
            End If
        End Function

        'Private Sub Client_Errored(ByVal sender As Object, ByVal e As GenericEventArgs(Of KeyValuePair(Of String, String)))
        '    Me._LastErrors.Add(e.Item)
        'End Sub

        Private Sub ProcessException(ByVal client As CachingClientProvider, ByVal ex As Exception)
            'SetCurrentStatus(DistributedCachingStatus.InternalError)
            'If DistributedCachingProvider._Settings IsNot Nothing Then
            '    DistributedCachingProvider._Settings.Enabled = False
            'End If
            If client IsNot Nothing Then
                client.DisableTemporary()
            End If

            Aricie.DNN.Diagnostics.AsyncLogger.Instance.AddException(ex)

        End Sub


        Private _InTimerCallback As Integer

        Private Sub CheckCallBacks(ByVal state As Object)
            If Interlocked.Exchange(Me._InTimerCallback, 1) <> 0 Then
                Exit Sub
            End If

            Try
                Dim broadCasts As List(Of BroadCastInfo) = DirectCast(DistributedCachingProviderBase.Instance.GetItem(DistributedCachingProviderBase.Settings.CloudSettings.BroadcastKey), List(Of BroadCastInfo))
                Dim newCheck As DateTime = _LastCheck
                Dim tempCallBack As CacheItemRemovedCallback = Nothing
                For Each objBroadCast As BroadCastInfo In broadCasts
                    If objBroadCast.Time > _LastCheck AndAlso objBroadCast.Node <> DotNetNuke.Common.Globals.ServerName Then
                        If objBroadCast.Time > newCheck Then
                            newCheck = objBroadCast.Time
                        End If
                        If Me._EmulatedCallBacks.TryGetValue(objBroadCast.Key, tempCallBack) Then
                            tempCallBack.Invoke(objBroadCast.Key, Nothing, CacheItemRemovedReason.Removed)
                        End If
                    End If
                Next
                _LastCheck = newCheck
                Dim objPredicate As New BroadCastPredicate(Now.Subtract(Me.BroadCastExpiration))
                If broadCasts.RemoveAll(AddressOf objPredicate.Match) > 0 Then
                    DistributedCachingProviderBase.PutInDistributedCaching(DistributedCachingProviderBase.Settings.CloudSettings.BroadcastKey, broadCasts)
                End If
            Catch ex As Exception
                Me.ProcessException(Nothing, ex)
            Finally
                Interlocked.Exchange(_InTimerCallback, 0)
            End Try

        End Sub




        Private Sub BroadcastChange(ByVal key As String)
            Dim broadCasts As List(Of BroadCastInfo) = DirectCast(DistributedCachingProviderBase.Instance.GetItem(DistributedCachingProviderBase.Settings.CloudSettings.BroadcastKey), List(Of BroadCastInfo))
            If broadCasts Is Nothing Then
                broadCasts = New List(Of BroadCastInfo)
            End If
            broadCasts.Add(New BroadCastInfo(key, DotNetNuke.Common.Globals.ServerName))
            DistributedCachingProviderBase.PutInDistributedCaching(DistributedCachingProviderBase.Settings.CloudSettings.BroadcastKey, broadCasts)
        End Sub

        Private Class BroadCastPredicate

            Private _LimitDate As DateTime

            Public Sub New(ByVal limitDate As DateTime)
                Me._LimitDate = limitDate
            End Sub

            Public Function Match(ByVal objBroadCast As BroadCastInfo) As Boolean
                Return objBroadCast.Time < Me._LimitDate
            End Function

        End Class


#End Region






        Private disposedValue As Boolean = False        ' To detect redundant calls

        ' IDisposable
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    Me.Stop()
                End If

            End If
            Me.disposedValue = True
        End Sub

#Region " IDisposable Support "
        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class
End Namespace